package com.cmpe451.nutty.controller;

import com.cmpe451.nutty.apiresponses.*;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.commons.codec.digest.DigestUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.SessionAttributes;

import com.cmpe451.nutty.dao.UserDao;
import com.cmpe451.nutty.mobileapi.ApiErrorCode;
import com.cmpe451.nutty.mobileapi.ApiThreadLocal;
import com.cmpe451.nutty.model.ApiUser;
import com.cmpe451.nutty.model.Comment;
import com.cmpe451.nutty.model.ExampleModel;
import com.cmpe451.nutty.model.Favorite;
import com.cmpe451.nutty.model.Follow;
import com.cmpe451.nutty.model.Ingredients;
import com.cmpe451.nutty.model.Rate;
import com.cmpe451.nutty.model.Recipe;
import com.cmpe451.nutty.model.Role;
import com.cmpe451.nutty.model.Style;
import com.cmpe451.nutty.model.Tools;
import com.cmpe451.nutty.model.Unit;
import com.cmpe451.nutty.model.User;
import com.cmpe451.nutty.service.MailService;
import com.cmpe451.nutty.service.RecipeService;
import com.cmpe451.nutty.service.TestService;
import com.cmpe451.nutty.service.UserService;
import com.cmpe451.nutty.so.GuiUser;
import com.cmpe451.nutty.utils.JsonResponseException;
import com.cmpe451.nutty.utils.MyLogger;
import com.google.gson.Gson;


/**
 * @author      CmpE Group 2 Fall 2014 nuttyconfirmation@gmail.com
 * @version     2.0
 * @since       06.01.2015
 */

@Controller
@SessionAttributes("userid")
@RequestMapping(value = "/api")
public class SampleJSONControllerV10 extends HomeController {
	private final MyLogger log = MyLogger.getLogger(getClass());

	@Autowired
	private TestService twitterService;
	
	@Autowired
	private TestService userService;

	@Autowired
	private UserService userDetailsService;

	@Autowired
	private UserDao userDao;

	@Autowired
	private HttpSession httpSession;
	
	@Autowired
	private RecipeService recipeService;
	
	@Autowired
	private MailService mailService;

	@Autowired
	private HttpServletRequest request;

	public JsonResponse response;
	public SuccessResponse successResp;
	public Gson gson;
	public JsonError err;

	
	
	/**
	 * Default constructor
	 * <p>
	 * Every JSON controller instantiation should contain these fields since these will be used for returning data.
	 * <p>
	 *
	 * @param  response The variable that will be returned for each query from android
	 * @param  gson The framework which will convert given response object to Json
	 * @param  successResp The object which determines the success of the query
	 * @param  err If success if false, then a error message is returned.
	 * @return Null since it is constructor
	 */
	
	public SampleJSONControllerV10() {
		response = new JsonResponse();
		gson = new Gson();
		successResp = new SuccessResponse();
		err = new JsonError();
	}

	/**
	 * Registration
	 * <p>
	 * Mobile API for the registration of the user. 
	 * <p>
	 *
	 * @param  username Name of the user to be registered
	 * @param  password Password of the user to be registered
	 * @param  email Email of the user to be registered. It is used for sending activation email
	 * 
	 * @return response The status of the query. If there exists a user with that email or username, then response is false.
	 */
	
	@RequestMapping(value = "registerUser", method = RequestMethod.GET)
	public @ResponseBody Object registerUser(@RequestParam String username,
			@RequestParam String password, @RequestParam String email) throws NoSuchAlgorithmException{
		GuiUser userWithName = (GuiUser) userDetailsService.loadUserByUsername(username);
		Role role = userDetailsService.getRoleByName("USER");
		User userWithEmail =  userDetailsService.getUserByEmail(email);
		if(userWithEmail != null){
			response.success = false;
			response.data = err.errorCreater(5);
		}
		else if (userWithName == null) {
			User newUser = new User();
			
			newUser.setUsername(username);
			newUser.setPassword(password);
			newUser.setEmail(email);
			newUser.getRoles().add(role);
			newUser.setAge(0L);
			newUser.setBio("");
			newUser.setHometown("");
			newUser.setLevel_point(0L);
			newUser.setName("");
			newUser.setValid(0);
			
			String modifiedUrl = makeUrl(request).replaceFirst("api/registerUser", "saveUser");
			userService.saveModel(newUser);
			mailService.sendConfirmationMail(newUser, modifiedUrl);
			
			response.success = true;
			response.data = successResp;
			
		} 
		else {
			response.success = false;
			response.data = err.errorCreater(0);
		}
		return gson.toJson(response);
	}
	
	/**
	 * Login function for mobile API
	 * <p>
	 * Used for authenticating the user and setting the http session field for the userid
	 * <p>
	 *
	 * @param  username The username of the user to be logged in
	 * @param  password The password of the user to be logged in
	 * @return response If the username does not exists or the password and username do not match, the response success is false. Else it will set httpSession and return true.
	 */

	@RequestMapping(value = "login", method = RequestMethod.GET)
	public @ResponseBody Object login(@RequestParam String username,
			@RequestParam String password) {
		GuiUser userWithName = (GuiUser) userDetailsService.loadUserByUsername(username);
		if(userWithName == null){
			response.success = false;
			response.data = err.errorCreater(4);
		}
		else if (userWithName.getPassword().equals(password)) {
			httpSession.setAttribute("userid", userWithName.getId());
			UserResponse userResp = new UserResponse();
			userResp.id = userWithName.getId();
			userResp.followedBy = false;
			userResp.following = false;
			userResp.username = userWithName.getUsername();
			response.data = userResp;
			response.success = true;
			
		} 
		else {
			response.success = false;
			response.data = err.errorCreater(3);
		}
		return gson.toJson(response);
	}
	
	/**
	 * Logout function will logout the logged in user
	 * <p>
	 * If there exists a user who is logged in, tis function will logout that user. It will remove the http session attribute
	 * <p>
	 *
	 * @return response If a logged in user exists, returns true. Else if will return false.
	 */
	
	@RequestMapping(value = "apilogout", method = RequestMethod.GET)
	public @ResponseBody Object apilogout() {
		if(httpSession.getAttribute("userid") == null){
			response.success = false;
			response.data = err.errorCreater(2);
		}
		else{
			User userWithName = userDetailsService.getUserById((Long)httpSession.getAttribute("userid"));
		
			if(userWithName == null){
				response.success = false;
				response.data = err.errorCreater(2);
			}
			else{
				httpSession.invalidate();
				response.success = true;
				response.data = successResp;
			}
		}
		return gson.toJson(response);
	}
	
	/**
	 * Returns current users info.
	 * <p>
	 * This method returns all the information about the currently logged in user.
	 * <p>
	 *
	 * @return response Returns all the information of the user stored in the database if there exists a logged in user.
	 */

	@RequestMapping(value = "getMyDetailedInfo", method = RequestMethod.GET)
	public @ResponseBody Object getMyDetailedInfo() {
		if(httpSession.getAttribute("userid") == null){
			response.success = false;
			response.data = err.errorCreater(2);
		}
		else{
			User thisUser = userDetailsService.getUserById((Long) httpSession
				.getAttribute("userid"));
			DetailedInfoResponse userInfo = new DetailedInfoResponse();
			userInfo.age = thisUser.getAge().intValue();
			userInfo.bio = thisUser.getBio();
			userInfo.chefLevel = thisUser.getLevel_point().intValue();
			userInfo.name = thisUser.getName();
			
			UserResponse userResp = new UserResponse();
			userResp.id = thisUser.getId();
			userResp.username = thisUser.getUsername();
			userInfo.user = userResp;
			
			userInfo.hometown = thisUser.getHometown();
			response.data = userInfo;
			response.success = true;
		}
		return gson.toJson(response);
	}
	
	/**
	 * Returns the followers of the user
	 * <p>
	 * Checks if there exists a logged in user. If found, gets the followers and returns them.
	 * <p>
	 *
	 * @return response If there does not exists a logged in user, returns success false. If exists, finds and returns a list of users who follow this user.
	 */

	@RequestMapping(value = "getFollowers", method = RequestMethod.GET)
	public @ResponseBody Object getFollowers() {
		
		if(httpSession.getAttribute("userid") == null){
			response.success = false;
			response.data = err.errorCreater(2);
		}
		else{
			User thisUser = userDetailsService.getUserById((Long) httpSession.getAttribute("userid"));
			FollowersResponse followerListResp = new FollowersResponse();
			List<Follow> followList =  userService.getFollowings(thisUser.getUsername());
			for(Follow f: followList){
				User theFollower = userDetailsService.getUserByName(f.getName());
				UserResponse temp = new UserResponse();
				temp.username = theFollower.getUsername();
				temp.followedBy = true;
				temp.following = !isFollowPossible(thisUser.getUsername(),theFollower.getUsername());
				temp.id = (long) theFollower.getId();
				followerListResp.followers.add(temp);
			}
			response.data = followerListResp;
			response.success = true;
		}
		return gson.toJson(response);
	}
	
	/**
	 * Returns the users who this user follows
	 * <p>
	 * Checks if there exists a logged in user. If found, finds the users who this user follows and returns the list.
	 * <p>
	 *
	 * @return response If there does not exists a logged in user, returns success false. If exists, finds and returns a list of users who this user follows.
	 */

	@RequestMapping(value = "getFollowing", method = RequestMethod.GET)
	public @ResponseBody Object getFollowing() {
		if(httpSession.getAttribute("userid") == null){
			response.success = false;
			response.data = err.errorCreater(2);
		}
		else{
			GuiUser thisUser = userDetailsService.getGuiUserById((Long) httpSession
					.getAttribute("userid"));
				FollowingResponse followingList = new FollowingResponse();
				List<Follow> followList =  userService.getFollowers(thisUser.getUsername());
				for(Follow following: followList){
					UserResponse temp = new UserResponse();
					temp.username = following.getFollows();
					temp.followedBy = !isFollowPossible(temp.username,thisUser.getUsername());
					temp.following = true;
					temp.id = userDetailsService.getUserByName(temp.username).getId();
					followingList.following.add(temp);
				}

			response.data = followingList;
			response.success = true;

		}
		return gson.toJson(response);
	}
	
	/**
	 * Returns the recipe information
	 * <p>
	 * Finds the recipe and sets up the response. Returns error if there does not exists a recipe with that id.
	 * <p>
	 *
	 * @param  recipeid  
	 * @return response Recipe information is set into the response object and sent as a json object 
	 */

	@RequestMapping(value = "getRecipe", method = RequestMethod.GET)
	public @ResponseBody Object getRecipe(@RequestParam long recipeid) {
		if(httpSession.getAttribute("userid") == null){
			response.success = false;
			response.data = err.errorCreater(2);
		}
		else{
			User thisUser = userDetailsService.getUserById((Long)httpSession
				.getAttribute("userid"));
			if(thisUser == null){
				response.success = false;
				response.data = err.errorCreater(8);
			}
			else{
				Recipe thisRecipe = recipeService.getRecipeById(recipeid);
				if(thisRecipe == null){
					response.success = false;
					response.data = err.errorCreater(6);
				}
				else{
					RecipeResponse recipe = new RecipeResponse();
					recipe.ownerId = userDetailsService.getUserByName(thisRecipe.getRecipe_owner()).getId();
					recipe.ownerName = thisRecipe.getRecipe_owner();
					recipe.ratingCount = thisRecipe.getRating_count();
					recipe.recipeId = recipeid;
					recipe.recipeName = thisRecipe.getRecipe_name();
					recipe.totalRating = thisRecipe.getTotal_rating().intValue();
				
					response.data = recipe;
					response.success = true;
				}
			}
		}
		return gson.toJson(response);
	}
	
	/**
	 * Favorites the recipe  
	 * <p>
	 * Adds the recipe to the users' favorite recipe list
	 * <p>
	 *
	 * @param  recipeid Recipe identifier of the recipe to be favorited
	 * @return response If there does not exists a logged in user or the recipe id point to a non-existing recipe, returns false.
	 */

	@RequestMapping(value = "favRecipe", method = RequestMethod.GET)
	public @ResponseBody Object favRecipe(@RequestParam long recipeid) {
		if(httpSession.getAttribute("userid") == null){
			response.success = false;
			response.data = err.errorCreater(2);
		}
		else if(recipeService.getRecipeById(recipeid) == null){
			response.success = false;
			response.data = err.errorCreater(6);
		}
		else{
			User thisUser = userDetailsService.getUserById((Long)httpSession.getAttribute("userid"));
			if(recipeService.getFav(thisUser.getId(), recipeid) != null){
				response.data = err.errorCreater(10);
				response.success = false;
			}
			else{
				Favorite thisFav = new Favorite();
				thisFav.setUserId(thisUser.getId());
				thisFav.setRecipeId(recipeid);

				recipeService.saveFav(thisFav);
				response.data = successResp;
				response.success = true;
				
			}
		}
		return gson.toJson(response);
	}
	
	/**
	 * Rates the recipe according to the users' rate
	 * <p>
	 * Gets the recipe id and the rating and adds to the recipes' parameters
	 * <p>
	 *
	 * @param  recipeid The identifier of the recipe to be rated
	 * @param  rating The rating count of the user
	 * @return response If there does not exists a logged in user or the recipe id is wrong, returns false. Else returns success true.
	 */

	@RequestMapping(value = "rateRecipe", method = RequestMethod.GET)
	public @ResponseBody Object rateRecipe(@RequestParam long recipeid,
			@RequestParam int rating) {
		Recipe thisRecipe = recipeService.getRecipeById(recipeid);
		if(httpSession.getAttribute("userid") == null){
			response.success = false;
			response.data = err.errorCreater(2);
		}
		else if(thisRecipe == null){
			response.success = false;
			response.data = err.errorCreater(6);
		}
		else{
			
			User thisUser = userDetailsService.getUserById((Long) httpSession.getAttribute("userid"));
			Rate prevRate = recipeService.findRatebyRecipe(thisUser.getUsername(), recipeid);
			if(prevRate != null){
				
				response.success = false;
				response.data = err.errorCreater(9);
				return gson.toJson(response);
			}
			
			List<Rate> ratesofaRecipe = recipeService.getTotalRate(thisRecipe);
			String currentUsername = thisUser.getUsername();
			Rate oldRate = new Rate();
			for (Rate r : ratesofaRecipe) {
				if (r.getUsername().equals(thisUser.getUsername()))
					oldRate = r;
			}
			if (oldRate.getUsername() != null) {
				oldRate.setRate(rating);
				recipeService.updateRate(oldRate);
			} 
			else {
				Rate newRate = new Rate();
				newRate.setRate(rating);
				newRate.setRecipe_id(recipeid);
				newRate.setUsername(currentUsername);
				thisUser.setLevel_point(thisUser.getLevel_point() + 5L);
				userDetailsService.updateUser(thisUser);
				recipeService.saveRate(newRate);
			}

			List<Rate> ratesofaRecipeNew = recipeService.getTotalRate(thisRecipe);
			int sum = 0;
			for (Rate r : ratesofaRecipeNew) {
				sum += r.getRate();
			}
			int totalCount = ratesofaRecipeNew.size();
			thisRecipe.setRating_count(totalCount);
			thisRecipe.setRate((double) sum / totalCount);
			thisRecipe.setTotal_rating((long)sum);
			recipeService.updateRecipe(thisRecipe);
			
			response.data = successResp;
			response.success = true;

		}
		return gson.toJson(response);
	}
	
	/**
	 * Follows a given user
	 * <p>
	 * Creates a new object to determine the follow relation between two users
	 * <p>
	 *
	 * @param  userid The identifier of the user to be followed
	 * @return response The response object of to be changed to json.
	 */

	@RequestMapping(value = "followUser", method = RequestMethod.GET)
	public @ResponseBody Object followUser(@RequestParam long userid) {
		if(httpSession.getAttribute("userid") == null){
			response.success = false;
			response.data = err.errorCreater(2);
		}
		else{
			User thisUser = userDetailsService.getUserById((Long) httpSession
					.getAttribute("userid"));
			
			Follow follow = new Follow();
			follow.setName(thisUser.getUsername());
			User toFollow = userDetailsService.getUserById((Long)userid);
			follow.setFollows(toFollow.getUsername());
			
			follow.setName_id(thisUser.getId());
			follow.setFollows_id(toFollow.getId());
			twitterService.saveFollow(follow);

			thisUser.setLevel_point(thisUser.getLevel_point() + 10L);
			userDetailsService.updateUser(thisUser);
			
			toFollow.setLevel_point(toFollow.getLevel_point() + 15L);
			userDetailsService.updateUser(toFollow);
			
			response.data = successResp;
			response.success = true;

		}
		return gson.toJson(response);
	}
	
	/**
	 * Unfollows the user
	 * <p>
	 * Removes the object which points to the relation between two users.
	 * <p>
	 *
	 * @param  userid The identifier of the user to be unfollowed
	 * @return response The response object carries information about the state of the query
	 */

	@RequestMapping(value = "unfollowUser", method = RequestMethod.GET)
	public @ResponseBody Object unfollowUser(@RequestParam long userid) {
		if(httpSession.getAttribute("userid") == null){
			response.success = false;
			response.data = err.errorCreater(2);
		}
		else{
			User thisUser = userDetailsService.getUserById((Long) httpSession
					.getAttribute("userid"));
			Follow follow = twitterService.findFollowship(thisUser.getUsername(), userDetailsService.getGuiUserById((Long)userid).getUsername());
			if(follow == null){
				response.data = err.errorCreater(7);
				response.success = false;
			}
			else{
				twitterService.deleteFollow(follow);
				response.data = successResp;
				response.success = true;
			}
		}
		return gson.toJson(response);
	}
	
	/**
	 * Updated user info
	 * <p>
	 * Updates the user information according the the data given
	 * <p>
	 *
	 * @param  name Used if the user wants to change his/her name.
	 * @param  bio A short description about the user.
	 * @param  age The age of the user
	 * @param  hometown The home town of the user
	 * @return response Updates the users' info and returns the success object if there exists a logged in user.
	 */

	@RequestMapping(value = "setMyDetailedInfo", method = RequestMethod.GET)
	public @ResponseBody Object setMyDetailedInfo(@RequestParam(required = false) String name,
			@RequestParam(required = false) String bio, @RequestParam(required = false) Integer age,
			@RequestParam(required = false) String hometown) {
		if(httpSession.getAttribute("userid") == null){
			response.success = false;
			response.data = err.errorCreater(2);
		}
		
		else{
			User userToUpdate = userDetailsService.getUserById((Long) httpSession
					.getAttribute("userid"));
			
			if(name != null)
				userToUpdate.setName(name);
			
			if(bio != null)
				userToUpdate.setBio(bio);
			
			if(age != null && age > 0 && age < 150)
				userToUpdate.setAge((long)age);
			
			if(hometown != null)
				userToUpdate.setHometown(hometown);
			
			userService.saveModel(userToUpdate);
			response.data = successResp;
			response.success = true;

		}
		return gson.toJson(response);
	}
	
	/**
	 * List of favorited recipes
	 * <p>
	 * Returns a list of recipes that were favorited by the user
	 * <p>
	 *
	 * @return response Returns the favorites of the user if there exists a logged in user or else, returns success false.
	 */

	@RequestMapping(value = "getMyFavoriteRecipes", method = RequestMethod.GET)
	public @ResponseBody Object getMyFavoriteRecipes() {
		if(httpSession.getAttribute("userid") == null){
			response.success = false;
			response.data = err.errorCreater(2);
		}
		else{
			User thisUser = userDetailsService.getUserById((Long) httpSession.getAttribute("userid"));
			RecipeListResponse recipeList = new RecipeListResponse();
			List<Favorite> favList = recipeService.findFavoritesByUserId(thisUser.getId());
			for(Favorite fav: favList){
				RecipeResponse recResp = new RecipeResponse();
				Recipe thisRecipe = recipeService.getRecipeById(fav.getRecipeId());
				
				recResp.ownerId = userDetailsService.getUserByName(thisRecipe.getRecipe_owner()).getId();
				recResp.ownerName = thisRecipe.getRecipe_owner();
				recResp.ratingCount = thisRecipe.getRating_count();
				recResp.recipeId = thisRecipe.getId();
				recResp.recipeName = thisRecipe.getRecipe_name();
				recResp.totalRating = (int) (thisRecipe.getRate()*thisRecipe.getRating_count());
				recipeList.recipes.add(recResp);
			}
			response.data = recipeList;
			response.success = true;

		}
		return gson.toJson(response);
	}
	
	/**
	 * Adds comments to the given recipe
	 * <p>
	 * Adds the users' comment to the recipe and returns true or false.
	 * <p>
	 *
	 * @param  recipeid Recipe id of the comment to be added
	 * @param  comment The string version of the comment to be added
	 * @return response Returns either success response or the error object depending on the existance of the recipeid
	 */

	@RequestMapping(value = "addRecipeComments", method = RequestMethod.GET)
	public @ResponseBody Object addRecipeComments(@RequestParam long recipeid,
			@RequestParam String comment) {
		if(httpSession.getAttribute("userid") == null){
			response.success = false;
			response.data = err.errorCreater(2);
		}
		else{
			User thisUser = userDetailsService.getUserById((Long) httpSession.getAttribute("userid"));
			
			Comment newComment = new Comment();
			newComment.setLikes(0L);
			newComment.setComment(comment);
			newComment.setRecipe_id(recipeid);
			newComment.setUser(thisUser.getUsername());
			newComment.setDislikes(0L);
			newComment.setUserid(thisUser.getId());
			newComment.setUser_like("");
			if(recipeService.getRecipeById(recipeid) != null){
				recipeService.saveComment(newComment);
				thisUser.setLevel_point(thisUser.getLevel_point() + 10L);
				userDetailsService.updateUser(thisUser);
			}
			response.data = successResp;
			response.success = true;

		}
		return gson.toJson(response);
	}
	
	/**
	 * Returns all ingredients within the system
	 * <p>
	 *
	 * @return response All the ingredients within the system is returns as a list of ingredients.
	 */

	@RequestMapping(value = "getAllIngredients", method = RequestMethod.GET)
	public @ResponseBody Object getAllIngredients() {
		
		IngredientListResponse ingredientList = new IngredientListResponse();
		List<Ingredients> ingredients = recipeService.getIngredients();
		for(Ingredients ing: ingredients){
			IngredientResponse ingred = new IngredientResponse();
			ingred.id = ing.getId().intValue();
			ingred.name = ing.getName();
			ingredientList.ingredients.add(ingred);
		}
		response.data = ingredientList;
		response.success = true;

		return gson.toJson(response);
	}
	
	/**
	 * Returns all tools in the system
	 * <p>
	 * 
	 *  @return response List of all the tools within the system.
	 */

	@RequestMapping(value = "getAllTools", method = RequestMethod.GET)
	public @ResponseBody Object getAllTools() {

		KitchenToolListResponse toolList = new KitchenToolListResponse();
		List<Tools> tools = recipeService.getTools();
		for(Tools tool: tools){
			KitchenToolResponse kitchenTool = new KitchenToolResponse();
			kitchenTool.id = tool.getId().intValue();
			kitchenTool.name = tool.getTool_name();
			toolList.tools.add(kitchenTool);
		}

		response.data = toolList;
		response.success = true;

		return gson.toJson(response);
	}
	
	/**
	 * Returns all the styles within the system.
	 * <p>
	 *
	 * @return response A list of styles within the system.
	 */
	
	@RequestMapping(value = "getAllStyles", method = RequestMethod.GET)
	public @ResponseBody Object getAllStyles() {

		StyleListResponse styleListResp = new StyleListResponse();
		List<Style> styleList = recipeService.getStyles();
		for(Style style: styleList){
			StyleResponse s = new StyleResponse();
			s.id = style.getId().intValue();
			s.name = style.getName();
			styleListResp.styleList.add(s);
		}

		response.data = styleListResp;
		response.success = true;

		return gson.toJson(response);
	}
	
	/**
	 * Returns all the units within the system.
	 * <p>
	 *
	 * @return response the list of all the units within the system.
	 */
	
	@RequestMapping(value = "getAllUnits", method = RequestMethod.GET)
	public @ResponseBody Object getAllUnits() {

		UnitListResponse unitListResp = new UnitListResponse();
		List<Unit> unitList = recipeService.getUnits();
		for(Unit unit: unitList){
			UnitResponse u = new UnitResponse();
			u.id = unit.getId().intValue();
			u.name = unit.getName();
			unitListResp.unitList.add(u);
		}

		response.data = unitListResp;
		response.success = true;

		return gson.toJson(response);
	}
	
	/**
	 * Returns the detailed information of the recipe
	 * <p>
	 * Gets all the fields of the recipe and returns them to the user such as comments, ingredients, tools etc.
	 * <p>
	 *
	 * @param  recipeid Identifier of the recipe
	 * @return response Contains all the information related to a recipe
	 */

	@RequestMapping(value = "getRecipeDetails", method = RequestMethod.GET)
	public @ResponseBody Object getRecipeDetails(@RequestParam long recipeid) {
		RecipeDetailsResponse recipeDetail = new RecipeDetailsResponse();
		Recipe thisRecipe = recipeService.getRecipeById(recipeid);
		
		if(thisRecipe == null){
			response.success = false;
			response.data = err.errorCreater(6);
			return gson.toJson(response);
		}
		User userWithName = userDetailsService.getUserByName(thisRecipe.getRecipe_owner());
		
		List<Comment> recipeComments = recipeService.getCommentsById(recipeid);
		for(Comment recCom: recipeComments){
			RecipeCommentResponse recipeComment = new RecipeCommentResponse();
			recipeComment.commentId = recCom.getId().intValue();
			recipeComment.dislikeCount = recCom.getDislikes().intValue();
			recipeComment.message = recCom.getComment();
			recipeComment.likeCount = recCom.getLikes().intValue();
			User commentOwner = userDetailsService.getUserByName(recCom.getUser());
			recipeComment.ownerId = commentOwner.getId().intValue();
			recipeComment.ownerName = recCom.getUser();
			recipeDetail.comments.add(recipeComment);
		}
		recipeDetail.costLevel = thisRecipe.getCost().intValue();
		recipeDetail.description = thisRecipe.getDescription();
		recipeDetail.prepTime = thisRecipe.getPrep_time();
		RecipeResponse recipeResp = new RecipeResponse();
		recipeResp.ownerId = userWithName.getId();
		recipeResp.ownerName = thisRecipe.getRecipe_owner();
		recipeResp.ratingCount = thisRecipe.getRating_count();
		recipeResp.recipeId = thisRecipe.getId().intValue();
		recipeResp.recipeName = thisRecipe.getRecipe_name();
		recipeResp.totalRating = thisRecipe.getTotal_rating().intValue();
		recipeDetail.recipe = recipeResp;
		
		String recipeIngredientsString = thisRecipe.getIngredients_list();
		String[] recipeIngredients = amountParser(recipeIngredientsString,1);
		
		for(String ingRec: recipeIngredients){
			String[] split = ingRec.split("/");
			System.out.println("ingredient: " + ingRec);
			RecipeIngredientResponse recIngResp = new RecipeIngredientResponse();
			recIngResp.amount = Integer.parseInt(split[0]);
			recIngResp.unit = split[1];
			IngredientResponse thisIngredient = new IngredientResponse();
			Ingredients temp = recipeService.getIngredientByName(split[2]);
			
			thisIngredient.name = temp.getName();
			thisIngredient.id = temp.getId().intValue();
			recIngResp.ingredient = thisIngredient;
			if(split.length == 4)
				recIngResp.style = split[3];
			else
				recIngResp.style = "";
			recipeDetail.recipeIngredient.add(recIngResp);
		}
		
		String toolString = thisRecipe.getKitchen_tools();
		System.out.println("tools: " + toolString);
		if(toolString != null){
			String[] split = toolString.split("/");
			for(String s : split){
				KitchenToolResponse toolResp = new KitchenToolResponse();
				Tools t = recipeService.getToolByName(s);
				toolResp.id = t.getId().intValue();
				toolResp.name = t.getTool_name();
				recipeDetail.recipeTools.add(toolResp);
			}
		}
		response.data = recipeDetail;
		response.success = true;

		return gson.toJson(response);
	}
	
	/**
	 * Gets all the comments of the recipe
	 * <p>
	 * Searches and finds all comments within a recipe
	 * <p>
	 *
	 * @param  recipeid The identifier of the recipe to be searched
	 * @return response All the comments that belong to the recipe
	 */

	@RequestMapping(value = "getRecipeComments", method = RequestMethod.GET)
	public @ResponseBody Object getRecipeComments(@RequestParam long recipeid) {
		RecipeCommentListResponse commentListResp = new RecipeCommentListResponse();
		if(recipeService.getCommentsById(recipeid) == null){
			response.success = false;
			response.data = err.errorCreater(6);
		}
		else{
			
			List<Comment> commentList = recipeService.getCommentsById(recipeid);
			for(Comment c : commentList){
				RecipeCommentResponse comment = new RecipeCommentResponse();
				User commentOwner = userDetailsService.getUserByName(c.getUser());
				comment.commentId = c.getId().intValue();
				comment.dislikeCount = c.getDislikes().intValue();
				comment.likeCount = c.getLikes().intValue();
				comment.message = c.getComment();
				comment.ownerId = commentOwner.getId().intValue();
				comment.ownerName = commentOwner.getUsername();
				commentListResp.comments.add(comment);
			}
		}

		response.data = commentListResp;
		response.success = true;

		return gson.toJson(response);
	}
	
	/**
	 * Returns information of a user
	 * <p>
	 * Returns detailed information related to the searched user
	 * <p>
	 *
	 * @param  userid Identifier of the user whose information is asked
	 * @return response All the information related to that user
	 */

	@RequestMapping(value = "getDetailedUserInfo", method = RequestMethod.GET)
	public @ResponseBody Object getDetailedUserInfo(@RequestParam long userid) {
		
		DetailedInfoResponse thisUser = new DetailedInfoResponse();
		User theUser = userDetailsService.getUserById(userid);
		System.out.println("userid: " + userid);
		if(theUser == null){
			response.success = false;
			response.data = err.errorCreater(8);
			return gson.toJson(response);
		}
		if(theUser.getAge() != null)
			thisUser.age = theUser.getAge().intValue();
		else
			thisUser.age = 0;
		
		thisUser.bio = theUser.getBio();
		thisUser.chefLevel = theUser.getLevel_point().intValue();
		thisUser.hometown = theUser.getHometown();
		thisUser.name = theUser.getName();
		
		UserResponse userResp = new UserResponse();
		userResp.username = theUser.getUsername();
		userResp.id = userid;

		if(httpSession.getAttribute("userid") == null){
			userResp.followedBy = false;
			userResp.following = false;
		}
		else{
			User currentUser = userDetailsService.getUserById((Long)httpSession.getAttribute("userid"));
			userResp.followedBy = !isFollowPossible(theUser.getUsername(),currentUser.getUsername());
			userResp.following = !isFollowPossible(currentUser.getUsername(),theUser.getUsername());
		}
		thisUser.user = userResp;
		
		response.data = thisUser;
		response.success = true;

		return gson.toJson(response);
	}
	
	/**
	 * Recipes of the asked user
	 * <p>
	 *
	 * @param  userid The user whose recipes are asked
	 * @return response The recipes of the user
	 */

	@RequestMapping(value = "getUserRecipes", method = RequestMethod.GET)
	public @ResponseBody Object getUserRecipes(@RequestParam long userid) {
		if(httpSession.getAttribute("userid") == null){
			response.success = false;
			response.data = err.errorCreater(2);
		}
		else{
			User thisUser = userDetailsService.getUserById((Long) httpSession.getAttribute("userid"));
			
			GuiUser guiUser = userDetailsService.getGuiUserById(userid);

			List<Recipe> recipeList = recipeService.getRecipes(guiUser);

			RecipeListResponse recipeListResp = new RecipeListResponse();
			for(Recipe r : recipeList){
				RecipeResponse temp = new RecipeResponse();
				temp.ownerId = userid;
				temp.ownerName = r.getRecipe_owner();
				temp.ratingCount = r.getRating_count();
				temp.recipeId = r.getId();
				temp.recipeName = r.getRecipe_name();
				temp.totalRating = r.getTotal_rating().intValue();
				recipeListResp.recipes.add(temp);
			}
			response.data = recipeListResp;
			response.success = true;
		}

		return gson.toJson(response);
	}
	
	/**
	 * Top rated recipes
	 * <p>
	 *
	 * @param  count Used for determining maximum search results
	 * @return response Contains the highest rated recipes
	 */

	@RequestMapping(value = "getTopRecipes", method = RequestMethod.GET)
	public @ResponseBody Object getTopRecipes(@RequestParam int count) {
		
		RecipeListResponse recipeList = new RecipeListResponse();
		List<Recipe> topRecipes = new ArrayList<Recipe>();
		topRecipes = recipeService.findAllRecipes();
		Collections.sort(topRecipes, new Comparator<Recipe>() {
		        @Override
		        public int compare(Recipe o1, Recipe o2) {
		            return o2.getRate().compareTo(o1.getRate());
		        }
		});
		count = Math.min(count,20);
		topRecipes.subList(0, Math.min(count,topRecipes.size()));
		for(Recipe rec: topRecipes){
			 RecipeResponse recResp = new RecipeResponse();
			 recResp.ownerId = userDetailsService.getUserByName(rec.getRecipe_owner()).getId();
			 recResp.ownerName = rec.getRecipe_owner();
			 recResp.ratingCount = rec.getRating_count();
			 recResp.recipeId = rec.getId();
			 recResp.recipeName = rec.getRecipe_name();
			 recResp.totalRating = rec.getTotal_rating().intValue();
			 if(recipeList.recipes.size() < count)
				 recipeList.recipes.add(recResp);
			 else
				 break;
		 }
		
		response.data = recipeList;
		response.success = true;

		return gson.toJson(response);
	}
	
	/**
	 * Returns most recently added recipes
	 * <p>
	 *
	 * @param  count Used for determining maximum search results
	 * @return response Returns the list of the most recent recipes added
	 */
	
	@RequestMapping(value = "getRecentRecipes", method = RequestMethod.GET)
	public @ResponseBody Object getRecentRecipes(@RequestParam int count) {
		RecipeListResponse recipeList = new RecipeListResponse();
		List<Recipe> topRecipes = new ArrayList<Recipe>();
		topRecipes = recipeService.findAllRecipes();
		Collections.sort(topRecipes, new Comparator<Recipe>() {
		        @Override
		        public int compare(Recipe o1, Recipe o2) {
		            return o2.getCreateDate().compareTo(o1.getCreateDate());
		        }
		});
		count = Math.min(count,20);
		for(Recipe rec: topRecipes){
			RecipeResponse recResp = new RecipeResponse();
			recResp.ownerId = userDetailsService.getUserByName(rec.getRecipe_owner()).getId();
			recResp.ownerName = rec.getRecipe_owner();
			recResp.ratingCount = rec.getRating_count();
			recResp.recipeId = rec.getId();
			recResp.recipeName = rec.getRecipe_name();
			recResp.totalRating = rec.getTotal_rating().intValue();
			if(recipeList.recipes.size() < count)
				recipeList.recipes.add(recResp);
		}
		
		response.data = recipeList;
		response.success = true;

		return gson.toJson(response);
	}
	
	/**
	 * Recipe searching
	 * <p>
	 * Searches within the name field of all the recipes
	 * <p>
	 *
	 * @param  name Name to be searched within the title of the recipes
	 * @return response All recipes containing that keyword within their titles
	 * @throws IOException 
	 * @throws JSONException 
	 */

	@RequestMapping(value = "searchRecipe", method = RequestMethod.GET)
	public @ResponseBody Object searchRecipe(@RequestParam String name) throws IOException, JSONException {
		RecipeListResponse recipeList = new RecipeListResponse();
		
		URL url;
		InputStream is = null;
		BufferedReader br;
		String line;
		String[] tags = name.split(" ");
		Set<String> keywords = new HashSet<String>();
		for (String tag : tags) {
			url = new URL("http://conceptnet5.media.mit.edu/data/5.2/c/en/"
					+ tag + "?limit=5");
			is = url.openStream(); // throws an IOException
			br = new BufferedReader(new InputStreamReader(is));

			StringBuilder sb = new StringBuilder();

			while ((line = br.readLine()) != null) {
				sb.append(line);
				// System.out.println(line);
			}
			JSONObject json = new JSONObject(sb.toString());
			JSONArray arr = json.getJSONArray("edges");

			for (int i = 0; i < arr.length(); i++) {
				JSONArray texts = arr.getJSONObject(i).getJSONArray("text");

				for (int j = 0; j < texts.length(); j++) {
					if (!texts.getString(j).equals(tag)
							&& !texts.getString(j).equals("")
							&& !texts.getString(j).contains(" "))
						keywords.add(texts.getString(j));
				}

			}
			keywords.add(tag);

		}
		Set<Recipe> returnedRecipes = new HashSet<Recipe>();
		for (Iterator<String> it = keywords.iterator(); it.hasNext();) {
			String str = it.next();
			List<Recipe> recipes = recipeService
					.findRecipesBasedOnKeywords(str);
			for (Recipe r : recipes) {
				returnedRecipes.add(r);
			}
		}

		List<Recipe> foundRecipes = new ArrayList<Recipe>(returnedRecipes);
		List<Recipe> recipesWithName = recipeService.findSearchResults(name);
		for(Recipe r : recipesWithName){
			RecipeResponse thisRecipe = new RecipeResponse();
			GuiUser recipeOwner = (GuiUser) userDetailsService.loadUserByUsername(r.getRecipe_owner());
			thisRecipe.ownerId = recipeOwner.getId();
			thisRecipe.ownerName = r.getRecipe_owner();
			thisRecipe.recipeId = r.getId();
			thisRecipe.recipeName = r.getRecipe_name();
			thisRecipe.totalRating = r.getTotal_rating().intValue();
			thisRecipe.ratingCount = r.getRating_count();
			recipeList.recipes.add(thisRecipe);
		}
		
		int count = Math.min(foundRecipes.size(),15);
		for(int i = 0; i< count; i++){
			Recipe rec = foundRecipes.get(i);
			RecipeResponse thisRecipe = new RecipeResponse();
			GuiUser recipeOwner = (GuiUser) userDetailsService.loadUserByUsername(rec.getRecipe_owner());
			thisRecipe.ownerId = recipeOwner.getId();
			thisRecipe.ownerName = rec.getRecipe_owner();
			thisRecipe.recipeId = rec.getId();
			thisRecipe.recipeName = rec.getRecipe_name();
			thisRecipe.totalRating = rec.getTotal_rating().intValue();
			thisRecipe.ratingCount = rec.getRating_count();
			boolean doesExist = false;
			for(RecipeResponse r : recipeList.recipes){
				if(r.recipeId == thisRecipe.recipeId)
					doesExist = true;
			}
			if(!doesExist)
				recipeList.recipes.add(thisRecipe);
		}
		Collections.sort(recipeList.recipes, new Comparator<RecipeResponse>() {
	        @Override
	        public int compare(RecipeResponse o1, RecipeResponse o2) {
	        	Double rate1;
	        	if(o1.ratingCount == 0)
	        		rate1 = 0.0;
	        	else
	        		rate1 = ((double)o1.totalRating)/o1.ratingCount;
	        	Double rate2;
	        	
	        	if(o2.ratingCount == 0)
	        		rate2 = 0.0;
	        	else
	        		rate2 = ((double)o2.totalRating)/o2.ratingCount;
	        	
	        	return rate2.compareTo(rate1);
	        }
		});
		
		response.data = recipeList;
		response.success = true;
		
		return response;
	}
	
	@RequestMapping(value = "advancedSearch", method = RequestMethod.GET)
	public @ResponseBody Object advancedSearch(@RequestParam(required = false) String name,
			@RequestParam(required = false) Integer rating, @RequestParam(required = false) Integer cookTime,
			@RequestParam(required = false) Integer cost) {
		
		List<Recipe> recipes = new ArrayList<Recipe>();
		
		if(name != null && name.length() != 0 && !name.equalsIgnoreCase("x")){
			recipes = recipeService.findSearchResults(name);
		}
		else{
			recipes = recipeService.findAllRecipes();
		}
		
		if(cookTime != null && recipes.size()>0){
			for(int i = 0; i < recipes.size(); i++){
				Recipe rec = recipes.get(i);
				if(rec.getCook_time() + rec.getPrep_time() > cookTime.intValue()){
					recipes.remove(rec);
					i--;
				}
			}
		}
		
		if(rating != null && recipes.size()>0){
			for(int i = 0; i < recipes.size(); i++){
				Recipe rec = recipes.get(i);
				if(rec.getRate() < rating.intValue()){
					recipes.remove(rec);
					i--;
				}
			}
		}
		
		if(cost != null && recipes.size()>0){
			for(int i = 0; i < recipes.size(); i++){
				Recipe rec = recipes.get(i);
				if(rec.getCost() > cost){
					recipes.remove(rec);
					i--;
				}
			}
		}
		
		RecipeListResponse recList = new RecipeListResponse();
		for(Recipe rec: recipes){
			RecipeResponse recResp = new RecipeResponse();
			recResp.ownerId = userDetailsService.getUserByName(rec.getRecipe_owner()).getId();
			recResp.ownerName = rec.getRecipe_owner();
			recResp.ratingCount = rec.getRating_count();
			recResp.recipeId = rec.getId();
			recResp.recipeName = rec.getRecipe_name();
			recResp.totalRating = rec.getTotal_rating().intValue();
			recList.recipes.add(recResp);
		}
		
		response.data = recList;
		response.success = true;
		
		return response;
	}
	
	/**
	 * User searching
	 * <p>
	 * Searches for users whose usernames' contain the given string 
	 * <p>
	 *
	 * @param  username Text to be searched
	 * @return response Contains the list of users' whose usernames contain that string
	 */
	
	@RequestMapping(value = "searchUser", method = RequestMethod.GET)
	public @ResponseBody Object searchUser(@RequestParam String username) {
		if(httpSession.getAttribute("userid") == null){
			response.success = false;
			response.data = err.errorCreater(2);
			return response;
		}
		UserListResponse userList = new UserListResponse();
		User currentUser = userDetailsService.getUserById((Long)httpSession.getAttribute("userid"));
		List<User> users = userDetailsService.findSearchResultsofUsers(username);
		
		for(User temp: users){
			UserResponse thisUser = new UserResponse();
			thisUser.id = temp.getId();
			thisUser.username = temp.getUsername();
			thisUser.followedBy = !isFollowPossible(thisUser.username,currentUser.getUsername());
			thisUser.following = !isFollowPossible(currentUser.getUsername(),thisUser.username);
			userList.users.add(thisUser);
		}

		response.data = userList;
		response.success = true;

		return gson.toJson(response);
	}
	
	/**
	 * Generates an authentication token for the asked connection
	 * <p>
	 *
	 * @param  apiUser The user to be validated
	 * @return md5 hashed string built by using the id of the user
	 */
	
	public static String generateAuthToken(ApiUser apiUser) {
		StringBuilder builder = new StringBuilder();
		builder.append("API_USER_AUTH_TOKEN_");
		builder.append(DigestUtils.md5Hex(String.valueOf(apiUser.getId())));
		builder.append("sphinx");
		return DigestUtils.md5Hex(builder.toString());
	}
}
